package org.chen.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.JsonData;
import lombok.extern.slf4j.Slf4j;
import org.chen.constants.ArticleListType;
import org.chen.exception.NewsException;
import org.chen.mapper.ArticleElasticsearchRepository;
import org.chen.mapper.ArticleRepository;
import org.chen.model.elastic.ArticleIndex;
import org.chen.model.elastic.ArticleSearchResult;
import org.chen.model.elastic.SearchResult;
import org.chen.model.mongodb.ArticleDetailVO;
import org.chen.model.mongodb.ArticleDocument;
import org.chen.model.quey.UserArticleQueryParam;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

// 存储服务整合
@Service
@Slf4j
public class ArticleStorageService{
    private final ArticleRepository mongoRepository;
    private final ArticleElasticsearchRepository esRepository;
    private final MongoTemplate mongoTemplate;
    private final ElasticsearchClient esClient;
    private final StringRedisTemplate redisTemplate;

    // 添加类别常量
    public static final String CATEGORY_RECOMMEND = "recommend";
    public static final String CATEGORY_FOOTBALL = "football";
    public static final String CATEGORY_BASKETBALL = "basketball";
    public static final String CATEGORY_OTHERS = "others";
    public static final String CATEGORY_RUGBY = "rugby";
    public static final String CATEGORY_NBA = "nba";
    public static final String CATEGORY_FOOTBALL_TRANSFERS = "football-transfers";
    private final static String COUNT_KEY = "article:count:";

    // 推荐文章的时间窗口（最近7天）
    @Value("${recommend.days.window}")
    private int recommendDaysWindow;

    public ArticleStorageService(
            ArticleRepository mongoRepository,
            ArticleElasticsearchRepository esRepository,
            ElasticsearchClient esClient,
            MongoTemplate mongoTemplate,
            StringRedisTemplate redisTemplate) {
        this.mongoRepository = mongoRepository;
        this.esRepository = esRepository;
        this.esClient = esClient;
        this.redisTemplate = redisTemplate;
        this.mongoTemplate = mongoTemplate;
    }

    // 搜索实现
    public SearchResult searchArticles(String keyword,int page, int size) {
        try {

            // 构建搜索条件
            BoolQuery.Builder boolQuery = new BoolQuery.Builder();
            // 只查询已发布的文章
            boolQuery.must(TermQuery.of(t -> t
                    .field("status")
                    .value(1))._toQuery());
            // 关键词搜索
            if (StringUtils.hasText(keyword)) {
                boolQuery.must(MultiMatchQuery.of(m -> m
                        .fields("title^3", "author^2", "leadParagraphs","summary")
                        .query(keyword)
                        .type(TextQueryType.BestFields))._toQuery());
            }
            SearchRequest searchRequest = SearchRequest.of(r -> r
                    .index("articles")
                    .query(boolQuery.build()._toQuery())
                    .from((page - 1) * size)
                    .size(size)
                    .highlight(h -> h
                            .fields("title", f -> f.numberOfFragments(1))
                            .fields("leadParagraphs", f -> f.numberOfFragments(1))
                            .fields("summary", f -> f.numberOfFragments(1))
                            .preTags("<em>")
                            .postTags("</em>")));
            // 执行搜索并打印请求内容
            log.info("Search request: {}", searchRequest.toString());

            // 执行查询
            SearchResponse<ArticleIndex> response = esClient.search(searchRequest, ArticleIndex.class);

            // 处理搜索结果
            List<ArticleSearchResult> results = response.hits().hits().stream()
                    .map(hit -> {
                        ArticleIndex article = hit.source();
                        Map<String, List<String>> highlights = hit.highlight();
                        Double score = hit.score(); // 获取得分
                        return new ArticleSearchResult(article, score, highlights);
                    })
                    .toList();

            return new SearchResult(
                    results,
                    response.hits().total() != null ? response.hits().total().value() : 0
            );

        } catch (Exception e) {
            log.error("Error during search operation", e);
            throw new NewsException("Failed to perform search" + e);
        }
    }

    // 分页获取所选分类文章
    public Page<ArticleIndex> getArticleList(UserArticleQueryParam request) {
        try {

            // 1. 构建基础查询条件
            BoolQuery.Builder boolQuery = new BoolQuery.Builder();

            // 只查询已发布的文章
            boolQuery.must(TermQuery.of(t -> t
                    .field("status")
                    .value(1))._toQuery());

            // 2. 根据不同分类构建不同的查询和排序条件
            if (CATEGORY_RECOMMEND.equals(request.getCategory())) {
                // 推荐文章：优先展示被标记为推荐的文章
                return getRecommendedArticles(request.getPage(), request.getSize());
            } else {
                if (request.getCategory() != null) {
                    // 处理具体分类
                    String finalCategory = switch (request.getCategory()) {
                        case CATEGORY_BASKETBALL -> CATEGORY_BASKETBALL;
                        case CATEGORY_FOOTBALL -> CATEGORY_FOOTBALL;
                        case CATEGORY_NBA -> CATEGORY_NBA;
                        case CATEGORY_RUGBY -> CATEGORY_RUGBY;
                        case CATEGORY_FOOTBALL_TRANSFERS -> CATEGORY_FOOTBALL_TRANSFERS;
                        default -> CATEGORY_OTHERS;
                    };

                    // 添加分类条件
                    boolQuery.must(TermQuery.of(t -> t
                            .field("category")
                            .value(finalCategory))._toQuery());
                }
            }

            // 3. 构建排序条件
            List<SortOptions> sorts = new ArrayList<>();

            // 置顶排序最优先
            sorts.add(SortOptions.of(s -> s.field(f -> f
                    .field("isTop")
                    .order(SortOrder.Desc))));
            sorts.add(SortOptions.of(s -> s.field(f -> f
                    .field("topOrder")
                    .order(SortOrder.Desc))));

            if (request.getType().equals(ArticleListType.LATEST)){
                // 最新：直接按创建时间
                sorts.add(SortOptions.of(s -> s.field(f -> f
                        .field("createTime")
                        .order(SortOrder.Desc))));
            }else if (request.getType().equals(ArticleListType.HOT)){
                // 热门：先按热门标记和权重，再按浏览量最后按时间
                sorts.add(SortOptions.of(s -> s.field(f -> f
                        .field("isHot")
                        .order(SortOrder.Desc))));
                sorts.add(SortOptions.of(s -> s.field(f -> f
                        .field("hotOrder")
                        .order(SortOrder.Desc))));
                sorts.add(SortOptions.of(s -> s.field(f -> f
                        .field("viewCount")
                        .order(SortOrder.Desc))));
                sorts.add(SortOptions.of(s -> s.field(f -> f
                        .field("createTime")
                        .order(SortOrder.Desc))));
            }

            // 4. 构建搜索请求
            SearchRequest searchRequest = SearchRequest.of(r -> r
                    .index("articles")
                    .query(boolQuery.build()._toQuery())
                    .sort(sorts)
                    .from(request.getPage() * request.getSize())
                    .size(request.getSize()));

            return executeSearch(searchRequest, request.getPage(), request.getSize());

        } catch (Exception e) {
            log.error("Error fetching articles by category: {}", request.getCategory(), e);
            throw new NewsException("获取分类文章失败: " + e.getMessage());
        }
    }

    //获取推荐文章
    private Page<ArticleIndex> getRecommendedArticles(int page, int size) throws IOException {
        // 构建推荐文章的查询条件
        BoolQuery.Builder boolQuery = new BoolQuery.Builder();

        // 只查询已发布的文章
        boolQuery.must(TermQuery.of(t -> t
                .field("status")
                .value(1))._toQuery());

        // 获取7天前的时间并格式化
        LocalDateTime weekAgo = LocalDateTime.now()
                .minusDays(recommendDaysWindow)
                .withHour(0)
                .withMinute(0)
                .withSecond(0);

        String formattedDate = weekAgo.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

        log.debug("Searching for articles after: {}", formattedDate);

        boolQuery.must(RangeQuery.of(r -> r
                .field("createTime")
                .gte(JsonData.of(formattedDate)))._toQuery());

        // 构建排序条件
        List<SortOptions> sorts = new ArrayList<>();

        // 置顶最优先
        sorts.add(SortOptions.of(s -> s.field(f -> f
                .field("isTop")
                .order(SortOrder.Desc))));
        sorts.add(SortOptions.of(s -> s.field(f -> f
                .field("topOrder")
                .order(SortOrder.Desc))));

        // 推荐优先
        sorts.add(SortOptions.of(s -> s.field(f -> f
                .field("isRecommend")
                .order(SortOrder.Desc))));
        sorts.add(SortOptions.of(s -> s.field(f -> f
                .field("recommendOrder")
                .order(SortOrder.Desc))));

        // 最后是创建时间
        sorts.add(SortOptions.of(s -> s.field(f -> f
                .field("createTime")
                .order(SortOrder.Desc))));

        SearchRequest searchRequest = SearchRequest.of(r -> r
                .index("articles")
                .query(boolQuery.build()._toQuery())
                .sort(sorts)
                .from(page * size)
                .size(size));

        return executeSearch(searchRequest, page, size);
    }

    //执行查询方法
    private Page<ArticleIndex> executeSearch(SearchRequest searchRequest, int page, int size) throws IOException {
        // 执行搜索
        SearchResponse<ArticleIndex> response = esClient.search(searchRequest, ArticleIndex.class);

        // 获取总数
        assert response.hits().total() != null;
        long total = response.hits().total().value();

        // 处理搜索结果
        List<ArticleIndex> articles = new ArrayList<>();
        for (Hit<ArticleIndex> hit : response.hits().hits()) {
            if (hit.source() != null) {
                articles.add(hit.source());
            }
        }

        // 创建分页对象
        return new PageImpl<>(
                articles,
                PageRequest.of(page, size),
                total
        );
    }

    // 按作者搜索文章
    public Page<ArticleIndex> findByAuthor(String author, int page, int size) {
        PageRequest pageRequest = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createTime"));
        return esRepository.findByAuthor(author, pageRequest);
    }

    // 获取最新文章
    public Page<ArticleIndex> getLatestArticles(int limit) {
        UserArticleQueryParam latestParam = UserArticleQueryParam.builder()
                .type(ArticleListType.LATEST)
                .page(0)
                .size(limit)
                .build();

        return this.getArticleList(latestParam);
    }

    // 获取热门文章
    public Page<ArticleIndex> getPopularArticles(int limit) {
        UserArticleQueryParam hotParam = UserArticleQueryParam.builder()
                .type(ArticleListType.HOT)
                .page(0)
                .size(limit)
                .build();

        return this.getArticleList(hotParam);
    }

    // 获取文章详情
    // 1. 分离视图数据和阅读量
    @Transactional
    public ArticleDetailVO getArticleDetail(String id) {
        ArticleDocument article = mongoRepository.findById(id)
                .orElseThrow(() -> new NewsException("Article Not Found" + id));

        // 获取相关文章
        List<ArticleDocument> relatedArticles = getRelatedArticles(id);

        return ArticleDetailVO.builder()
                .article(article)
                .relatedArticles(relatedArticles)
                .build();
    }

    // 2. 单独获取阅读量的方法（不缓存）
    public Long getArticleViewCount(String id) {
        ArticleDocument article = mongoTemplate.findById(id, ArticleDocument.class);
        String s = redisTemplate.opsForValue().get(COUNT_KEY + id);
        if (article == null){
            return 0L;
        }else {
            if (s == null){
                return article.getViewCount();
            }else {
                return article.getViewCount() + Long.parseLong(s);
            }
        }
    }

    // 获取相关文章
    public List<ArticleDocument> getRelatedArticles(String id) {
        try {
            // 查找指定文章
            ArticleDocument article = mongoRepository.findById(id)
                    .orElseThrow(() -> new NewsException("Article Not Found: " + id));

            // 构建多重查询条件
            SearchRequest searchRequest = SearchRequest.of(s -> s
                    .index("articles")
                    .query(q -> q
                            .bool(b -> b
                                    // 使用标题和内容的组合查询
                                    .should(should -> should
                                            .moreLikeThis(m -> m
                                                    .fields("title", "contentHtml")
                                                    .like(l -> l.text(article.getTitle() + " " + article.getContentHtml()))
                                                    .minTermFreq(1)
                                                    .maxQueryTerms(12)
                                                    .minimumShouldMatch("30%")
                                                    .boost(2.0f)
                                            )
                                    )
                                    // 如果有分类，添加分类匹配
                                    .should(should -> should
                                            .match(m -> m
                                                    .field("category")
                                                    .query(article.getCategory())
                                                    .boost(1.0f)
                                            )
                                    )
                                    // 排除完全相同的标题
                                    .mustNot(mustNot -> mustNot
                                            .match(m -> m
                                                    .field("title")
                                                    .query(article.getTitle())
                                            )
                                    )
                                    // 排除当前文章
                                    .mustNot(mustNot -> mustNot
                                            .ids(i -> i.values(id))
                                    )
                            )
                    )
                    // 使用复合排序
                    .sort(sort -> sort
                            .score(a -> a
                                    .order(SortOrder.Desc))  // 按相关度排序
                    )
                    .size(5)
            );

            // 执行搜索请求
            SearchResponse<ArticleIndex> response = esClient.search(searchRequest, ArticleIndex.class);

            // 转换搜索结果，并过滤掉重复标题
            List<ArticleDocument> relatedArticles = response.hits().hits().stream()
                    .map(hit -> mongoRepository.findById(hit.id()))
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    // 使用 LinkedHashMap 保持顺序的同时去除重复标题
                    .collect(Collectors.collectingAndThen(
                            Collectors.toMap(
                                    ArticleDocument::getTitle,  // 用标题作为 key
                                    article1 -> article1,         // 值为文章本身
                                    (existing, replacement) -> existing,  // 如果有重复，保留第一个
                                    LinkedHashMap::new          // 使用 LinkedHashMap 保持顺序
                            ),
                            map -> new ArrayList<>(map.values())
                    ));

            // 如果相关文章不足5篇，补充随机文章
            if (relatedArticles.size() < 5) {
                List<ArticleDocument> randomArticles = getRandomArticles(id, 5 - relatedArticles.size(), relatedArticles);
                relatedArticles.addAll(randomArticles);
            }

            return relatedArticles;

        } catch (Exception e) {
            log.error("Error getting related articles for: {}", id, e);
            return Collections.emptyList();
        }
    }

    // 从 MongoDB 中随机获取指定数量的文章
    private List<ArticleDocument> getRandomArticles(String currentId, int count, List<ArticleDocument> excludeArticles) {
        // 构建排除条件
        Set<String> excludeIds = new HashSet<>();
        excludeIds.add(currentId);
        excludeIds.addAll(excludeArticles.stream().map(ArticleDocument::getId).collect(Collectors.toSet()));

        // 使用聚合查询随机获取文章
        Aggregation aggregation = Aggregation.newAggregation(
                // 排除已有的文章
                Aggregation.match(Criteria.where("_id").nin(excludeIds)),
                // 随机排序
                Aggregation.sample(count)
        );

        return mongoTemplate.aggregate(aggregation, "articles", ArticleDocument.class)
                .getMappedResults();
    }
}
